﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Xml;
using Mesuro.Common;
using Mesuro.Graphing.Utilities;
using System.Collections.Specialized;

namespace Mesuro.Graphing {
	[TemplatePart( Name = "PART_TitleTextBox", Type = typeof( TextBox ) )]
	public class Graph : MultiSelector, IXmlSerialisable, INotifyPropertyChanged {

		#region DP: IsIcon
		public static readonly DependencyProperty IsIconProperty = DependencyProperty.RegisterAttached( "IsIcon", typeof( bool ), typeof( Graph ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.Inherits, IsIconChangedStatic ) );
		public bool IsIcon {
			get { return (bool)GetValue( IsIconProperty ); }
			set { SetValue( IsIconProperty, value ); }
		}
		public static bool GetIsIcon( DependencyObject element ) { return (bool)element.GetValue( IsIconProperty ); }
		public static void SetIsIcon( DependencyObject element, bool value ) { element.SetValue( IsIconProperty, value ); }
		private static void IsIconChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: PlotInsets
		public static readonly DependencyProperty PlotInsetsProperty = DependencyProperty.RegisterAttached( "PlotInsets", typeof( Thickness ), typeof( Graph ), new FrameworkPropertyMetadata( new Thickness( 0 ), FrameworkPropertyMetadataOptions.Inherits, PlotInsetsChangedStatic ) );
		public Thickness PlotInsets {
			get { return (Thickness)GetValue( PlotInsetsProperty ); }
			set { SetValue( PlotInsetsProperty, value ); }
		}
		public static Thickness GetPlotInsets( DependencyObject element ) { return (Thickness)element.GetValue( PlotInsetsProperty ); }
		public static void SetPlotInsets( DependencyObject element, Thickness value ) { element.SetValue( PlotInsetsProperty, value ); }
		private static void PlotInsetsChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Graph g = sender as Graph;
			if (g != null) {
				g.InvalidateMeasure();
			}
		}
		#endregion

		#region DP: AxesType
		public static readonly DependencyProperty AxesTypeProperty = DependencyProperty.RegisterAttached( "AxesType", typeof( string ), typeof( Graph ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, AxesTypeChangedStatic, AxesTypeCoerceStatic ) );
		public string AxesType {
			get { return (string)GetValue( AxesTypeProperty ); }
			set { SetValue( AxesTypeProperty, value ); }
		}
		public static string GetAxesType( UIElement element ) { return (string)element.GetValue( AxesTypeProperty ); }
		public static void SetAxesType( UIElement element, string value ) { element.SetValue( AxesTypeProperty, value ); }
		private static void AxesTypeChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Graph graph = sender as Graph;
			if (graph != null) {
				graph.OnAxesTypeChanged( (string)e.OldValue, (string)e.NewValue );
			}
		}
		private static object AxesTypeCoerceStatic( DependencyObject sender, object baseValue ) {
			if (baseValue == null)
				return DependencyProperty.UnsetValue;
			else
				return baseValue;
		}

		public static readonly RoutedEvent AxesTypeChangedEvent = EventManager.RegisterRoutedEvent( "AxesTypeChanged", RoutingStrategy.Direct, typeof( RoutedPropertyChangedEventHandler<string> ), typeof( Graph ) );
		public event RoutedPropertyChangedEventHandler<string> AxesTypeChanged {
			add { AddHandler( AxesTypeChangedEvent, value ); }
			remove { RemoveHandler( AxesTypeChangedEvent, value ); }
		}
		public static void AddAxesTypeChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<string> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).AddHandler( AxesTypeChangedEvent, handler );
		}
		public static void RemoveAxesTypeChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<string> handler ) {
			if (d != null && d is UIElement)
				((UIElement)d).RemoveHandler( AxesTypeChangedEvent, handler );
		}
		#endregion

		#region DP: Axes
		private static readonly DependencyPropertyKey AxesPropertyKey = DependencyProperty.RegisterAttachedReadOnly( "Axes", typeof( Axes ), typeof( Graph ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.Inherits, AxesChangedStatic ) );
		public Axes Axes {
			get { return (Axes)GetValue( AxesPropertyKey.DependencyProperty ); }
			private set { SetValue( AxesPropertyKey, value ); }
		}
		public static Axes GetAxes( DependencyObject element ) { return (Axes)element.GetValue( AxesPropertyKey.DependencyProperty ); }
		private static void AxesChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: ShowTitle
		public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.RegisterAttached( "ShowTitle", typeof( bool ), typeof( Graph ), new FrameworkPropertyMetadata( true, FrameworkPropertyMetadataOptions.None, ShowTitleChangedStatic ) );
		public bool ShowTitle {
			get { return (bool)GetValue( ShowTitleProperty ); }
			set { SetValue( ShowTitleProperty, value ); }
		}
		public static bool GetShowTitle( DependencyObject element ) { return (bool)element.GetValue( ShowTitleProperty ); }
		public static void SetShowTitle( DependencyObject element, bool value ) { element.SetValue( ShowTitleProperty, value ); }
		private static void ShowTitleChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: Title
		public static readonly DependencyProperty TitleProperty = DependencyProperty.RegisterAttached( "Title", typeof( string ), typeof( Graph ), new FrameworkPropertyMetadata( "Graph", FrameworkPropertyMetadataOptions.None, TitleChangedStatic ) );
		public string Title {
			get { return (string)GetValue( TitleProperty ); }
			set { SetValue( TitleProperty, value ); }
		}
		public static string GetTitle( DependencyObject element ) { return (string)element.GetValue( TitleProperty ); }
		public static void SetTitle( DependencyObject element, string value ) { element.SetValue( TitleProperty, value ); }
		private static void TitleChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		public IEnumerable<Plot> Plots {
			get { return Items.OfType<Plot>(); }
		}

		public const string PROP_SelectedPlot = "SelectedPlot";
		public Plot SelectedPlot {
			get {
				return SelectedItem as Plot;
			}
		}

		public IEnumerable<Plot> SelectedPlots {
			get {
				return SelectedItems.OfType<Plot>();
			}
		}

		static Graph() {
			//Allow styling
			DefaultStyleKeyProperty.OverrideMetadata( typeof( Graph ), new FrameworkPropertyMetadata( typeof( Graph ) ) );

			//Make Graphs Focusable
			FocusableProperty.OverrideMetadata( typeof( Graph ), new FrameworkPropertyMetadata( true ) );

			//Force Load Axes
			System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor( typeof( Axes ).TypeHandle );
			System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor( typeof( Plot ).TypeHandle );
		}

		public Graph() {
			this.HorizontalAlignment = HorizontalAlignment.Stretch;
			this.VerticalAlignment = VerticalAlignment.Stretch;

			CommandBindings.Add( new CommandBinding( ApplicationCommands.Delete, ( o, e ) => RemovePlot( SelectedPlot ), ( o, e ) => e.CanExecute = (SelectedPlot != null) ) );
			CommandBindings.Add( new CommandBinding( Commands.AddPlot, ( o, e ) => AddPlot( (e.Parameter as Plot) ?? new Plot( Axes.Dimensions ) ) ) );

			AddHandler( Axes.AxesLoadedEvent, (RoutedEventHandler)delegate( object sender, RoutedEventArgs e ) {
				Axes = e.OriginalSource as Axes;
				if (PropertyChanged != null)
					PropertyChanged( this, new PropertyChangedEventArgs( "Axes" ) );
			} );
		}

		public override void OnApplyTemplate() {
			base.OnApplyTemplate();

			TextBox tb = GetTemplateChild( "PART_TitleTextBox" ) as TextBox;
			tb.KeyUp += delegate( object sender, KeyEventArgs e ) {
				if (e.Key == Key.Enter)
					tb.RaiseEvent( new RoutedEventArgs( Utils.EnterPressedEvent ) );
			};
		}

		public void AddPlot( Plot plot ) {
			if (plot != null) {
				Items.Add( plot );
				plot.MouseUp += OnPlotMouseUp;
			}
		}
		public void RemovePlot( Plot plot ) {
			Items.Remove( plot );
			plot.MouseUp -= OnPlotMouseUp;
		}

		protected virtual void OnAxesTypeChanged( string oldValue, string newValue ) {
			RaiseEvent( new RoutedPropertyChangedEventArgs<string>( oldValue, newValue, AxesTypeChangedEvent ) );
			ItemsPanel = new ItemsPanelTemplate( GraphingRegistry.CreateAxesFactory( newValue ) );
		}

		protected override void OnSelectionChanged( SelectionChangedEventArgs e ) {
			base.OnSelectionChanged( e );
			RaisePropertyChanged( PROP_SelectedPlot );
		}

		protected virtual void OnPlotMouseUp( object sender, MouseButtonEventArgs e ) {
			//TODO: Handle ctrl / shift
			if (sender is Plot) {
				SelectedItem = (Plot)sender;
			}
			Focus();
		}

		public XmlElement XMLSerialise( XmlDocument doc ) {
			XmlElement el = doc.CreateElement( "mesuro:graph" );
			el.SetAttribute( "title", Title );
			el.SetAttribute( "axes-type", AxesType );

			el.AppendChild( doc.CreateComment( "Graph serialisation is not yet supported." ) );
			return el;
		}

		private void RaisePropertyChanged( string propertyName ) {
			if (PropertyChanged != null)
				PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
		}
		public event PropertyChangedEventHandler PropertyChanged;

		protected override void OnGotKeyboardFocus( KeyboardFocusChangedEventArgs e ) {
			base.OnGotKeyboardFocus( e );
		}
		protected override void OnLostKeyboardFocus( KeyboardFocusChangedEventArgs e ) {
			base.OnLostKeyboardFocus( e );
		}
	}

}
